البرمجة

محلل نحوي تعاودي بسيط جافا

تصميم محلّل نحوي تعاودي بسيط (Recursive Descent Parser) في جافا

ملخّص تمهيدي

يُعَدّ التحليل النحوي (Parsing) إحدى أهمّ مراحل بناء المترجمات (Compilers) والمفسِّرات (Interpreters). وفي صميم هذا التحليل يبرز المحلِّل التعاودي البسيط أو ما يُعرَف بـ Recursive Descent Parser، لكونه أسلوباً برمجياً مُباشِراً يُجسّد قواعد النحو (Grammar Rules) على هيئة دوالّ (Methods) تستدعي نفسها ذاتياً وفق بنية قواعد اللغة. يركّز هذا المقال على بناء محرّك كامل لهذه التقنية باللغة جافا، مع إيضاح المفاهيم النظرية، وهيكلة الشيفرة، وتقنيات التعامل مع الأخطاء، وإرشادات تحسين الأداء وقابليّة الصيانة، إضافة إلى جدول يوضّح علاقة كل إنتاج (Production) بدالّة التحليل المناظرة له.


محتويات المقال

  1. مقدّمة إلى علم النحو الشكلي

  2. تعريف التحليل التعاودي ونطاق استخدامه

  3. تحضير بيئة التطوير في جافا

  4. تصميم المكوّنات الرئيسة

    • المُمَيزات (Lexer / Tokenizer)

    • أصناف العقد النحوية (AST Nodes)

    • واجهة المِحوَر (Parser Interface)

  5. بناء المحلّل خطوة بخطوة

    • قراءة الرموز (Tokens) وإدارتها

    • مطابقة القواعد LL(1)

    • تنفيذ الدوالّ التعاودية

  6. إدارة الأخطاء واستخراج الرسائل الدلالية

  7. تحسين الأداء: التنبؤ القَبلي وحذف التراجع الخلفي

  8. توليد شجرة بناء المترجم (AST) وربطها بالمعالج الدلالي

  9. اختبار الوحدة (Unit Testing) والتغطية الشاملة

  10. حالات تطبيقية واقعية

  11. خاتمة علمية موجزة

  12. المراجع


1. مقدّمة إلى علم النحو الشكلي

ظهر علم النحو الشكلي (Formal Grammar) مع أعمال نوام تشومسكي لِتوصيف لغات الحاسوب واللغات الطبيعية على حدّ سواء. يرتكز هذا العلم على بنية رمزية لتوليد عبارات اللغة، بحيث تُصنَّف القواعد إلى أنماط، أهمّها:

  • النحو المنتظم (Regular Grammar)

  • النحو المستقلّ عن السياق (Context‑Free Grammar, CFG)

  • النحو الحساس للسياق (Context‑Sensitive)

  • النحو غير المقيَّد (Unrestricted)

تركّز المترجمات عادةً على النحو المستقلّ عن السياق، إذ يمكن تمثيله بسهولة بخوارزميات تحليل LL و LR.


2. تعريف التحليل التعاودي ونطاق استخدامه

التحليل التعاودي (Recursive Descent) ينتمي إلى عائلة محلّلات LL(1)، أي يقرأ من اليسار إلى اليمين (Left‑to‑Right) ويبني أوّل اشتقاق يساري (Leftmost Derivation) بالاعتماد على نظرة واحدة للأمام (One‑Token Lookahead). الفكرة الجوهرية بسيطة:

«اجعل كل إنتاج نحويّ دالّةً تستدعي دوالّاً أُخرى تمثّل الأبناء (Sub‑Productions)».

هذه التقنية تناسب اللغات الصغيرة أو المتوسّطة التي لا تحتوي على لبس عوائمة (Ambiguities) معقّدة ولا تتطلّب تنبّؤاً بعيد المدى.


3. تحضير بيئة التطوير في جافا

  1. JDK ≥ 17: الاستفادة من تحسينات النَّمْذَجة القياسيّة والسجلّات (Records).

  2. Maven أو Gradle لمتابعة الاعتماديّات وإدارة الاختبارات.

  3. مكتبة JUnit 5 لاختبار الوحدة.

nginx
mvn archetype:generate -DgroupId=com.parser -DartifactId=rdp-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

4. تصميم المكوّنات الرئيسة

أ. المُمَيّزات (Lexer)

يقسّم الملفّ النصّي إلى رموز (Tokens) مكوَّنة من نوع (Type) وقيمة (Lexeme) وموقع (Position). يُفضَّل استخدام آلة حالية منتهية (DFA) لضمان الأداء.

java
public record Token(TokenType type, String lexeme, int line, int column) {}

ب. أصناف العقد النحوية (AST Nodes)

تمثّل بنية البناء (AST) باستخدام سجلّات (Records) أو فئة (Class) راقية:

java
sealed interface Expr permits Binary, Unary, Literal {} record Binary(Expr left, Token op, Expr right) implements Expr {} // ...

ج. واجهة المحلّل

java
public interface Parser { Expr parseExpression(); }

5. بناء المحلّل خطوة بخطوة

قراءة الرموز وإدارتها

java
private Token lookahead() { return tokens.get(pos); } private Token consume(TokenType expected) { if (lookahead().type() != expected) throw error("Expected " + expected); return tokens.get(pos++); }

مطابقة القواعد LL(1)

افترض القاعدة:

python
Expr → Term Expr' Expr' → ( “+” | “-” ) Term Expr' | ε

تصبح:

java
Expr parseExpr() { Expr node = parseTerm(); while (match(TokenType.PLUS, TokenType.MINUS)) { Token op = previous(); Expr right = parseTerm(); node = new Binary(node, op, right); } return node; }

جدول علاقة الإنتاج–الدالّة

إنتاج نحوي (Production) البروتوتايب في جافا وصف موجز
Expr → Term Expr' Expr parseExpr() نقطة الدخول لتحليل التعبيرات
Term → Factor Term' Expr parseTerm() معالجة الضرب والقسمة
`Factor → NUMBER “(” Expr “)”` Expr parseFactor()

6. إدارة الأخطاء واستخراج الرسائل

  • استعادة المزامنة (Panic Mode): تخطّي الرموز حتى العثور على فاصل معروف مثل الفاصلة المنقوطة.

  • توفير Stack Trace نحوي مبسّط يُبرز موقع الخطأ، وتوقّعات الأنماط الصحيحة.


7. تحسين الأداء

  1. تنبّؤ قبلي (Predictive Parsing): استخدام جدول يحفظ إمكانات النظرة الواحدة لتجنّب التراجع الخلفي (Backtracking).

  2. تجميع المصفوفات بدل القوائم عندما يُعرَف الحجم مسبقاً.

  3. استخدام سجلات جافا لتجنّب الكلفة الإضافية للحقول القابلة للتغيير.


8. توليد شجرة البناء وربطها بالمعالج الدلالي

بعد الحصول على الـ AST، يُنظَّف العقد من الزوائد، ثم تُمرَّر إلى مرحلة التحقّق الدلالي (Type Checking) أو التفسير (Evaluation).


9. اختبار الوحدة والتغطية

java
@Test void testAddition() { Parser p = new RDParser("1 + 2"); Expr ast = p.parseExpr(); assertEquals("(+ 1 2)", ast.toString()); }

استخدم تقارير JaCoCo لضمان تغطية تفوق 90 %.


10. حالات تطبيقية واقعية

  • لغات توصيف المجال (DSL) في تطبيقات الأعمال.

  • أنظمة القواعد (Rule Engines) داخل سير العمل.

  • أجزاء من المفسّرات التفاعلية (REPL) للغات مبسّطة تعليمية.


11. خاتمة علمية موجزة

إنّ بناء محلّل نحوي تعاودي بسيط في جافا يمثّل تمريناً عملياً قيّماً يجمع بين النظرية والممارسة. تُظهر التجربة أنّ البنية التعاودية تُسهّل مطابقة القواعد النحوية، وتُضفي وضوحاً على الشيفرة، شرط ضبط التنبّؤ ومنع التراجع غير الضروري. بالنظر إلى المشروعات ذات الحجم الصغير والمتوسّط، يتفوّق هذا الأسلوب بمرونته وسرعته في التطوير، كما يتيح الانتقال السلس إلى تحسينات أكثر تعقيداً عند الحاجة.


12. المراجع

  • Aho, A. V., Lam, M. S., Sethi, R., & Ullman, J. D. Compilers: Principles, Techniques, and Tools (2nd ed.), Pearson, 2006.

  • Parr, T. Language Implementation Patterns, Pragmatic Bookshelf, 2010.